namespace FIREFLYX {
    /** 投影类型 */
    export enum ProjectionType {
        /** 透视 */
        PERSPECTIVE,
        /** 正交 */
        ORTHOGRAPHIC,
    }
    /** FOV 轴向 */
    export enum FOVAxis {
        VERTICAL,
        HORIZONTAL
    }

    /** 相机清理模式 */
    export enum CameraClearMode {
        /** 天空盒 */
        SKY_BOX,
        /** 单色 */
        COLOR,
        /** 仅清理深度 */
        DEPTH_ONLY,
        /** 不做任何清理操作 */
        NONE
    }

    /** 相机 */
    export class Camera extends Component {
        //相机标记
        protected static _useSign: ComponentUseSign = "camera";
        /**
         * 远裁剪面
         */
        public far: number = 1000;
        /**
         * 近裁剪面
         */
        public near: number = 0.01;
        /**
         * 视口矩形 ViewPort Rect
         */
        public viewPortRect: Rect = new Rect(0, 0, 1, 1);
        /** Projection 投影模式 */
        public projection: ProjectionType = ProjectionType.PERSPECTIVE;
        /** 透视投影-Field of View (角度制)*/
        public fov: number = 60 * toRadian;
        /** 相机 fov  轴向*/
        public fovAxis: FOVAxis = FOVAxis.VERTICAL;
        /** 正交投影 -正交 的视野size */
        public size: number = 5;
        /** 背景颜色 */
        public backGround: Color4 = new Color4(0.18, 0.3, 0.472, 1);
        /** 清理模式 */
        public clearMode: CameraClearMode = CameraClearMode.SKY_BOX;

        //view矩阵
        private _viewMatrix: Matrix = new Matrix();
        //投影矩阵
        private _projectionMatrix: Matrix = new Matrix();
        //视窗的 asp 值
        private _asp: number = 1;
        //相机ubo 对象
        private _ubo: Nullable<WebGLBuffer>;
        //
        private _uboBlockSize: number;
        //
        private _posF32arr: Float32Array;
        //
        private _targetTexture: Nullable<RenderTexture2D>;

        constructor() {
            super();
            //init ubo
            const gl = App.webgl2;
            this._ubo = gl.createBuffer();
            //bind start
            gl.bindBuffer(gl.UNIFORM_BUFFER, this._ubo);
            const camBlockSize = this._uboBlockSize = 4 * (16 + 16 + 4);                //初始化 buffer 设置大小 4 = float
            gl.bufferData(gl.UNIFORM_BUFFER, camBlockSize, gl.DYNAMIC_DRAW);
            //bind end
            gl.bindBuffer(gl.UNIFORM_BUFFER, null);
            //
            this._posF32arr = new Float32Array(3);
        }

        /** 相机的 ubo对象 ，用于存储相机的设置参数 */
        public get ubo() { return this._ubo; }
        /** ubo块byte占用大小 */
        public get uboBlockSize() { return this._uboBlockSize; }
        /** aspect 值 w / h */
        public get asp() { return this._asp; }

        /** 相机渲染输出目标纹理 */
        public get targetTexture() { return this._targetTexture; }
        public set targetTexture(val) {
            if (val == this._targetTexture) return;
            this._targetTexture = val;
        }

        /**
         * 刷新 ubo 的数据
         * @returns 
         */
        public refreshUboData() {
            if (!this.entity) return;
            const gl = App.webgl2;
            //bind start
            gl.bindBuffer(gl.UNIFORM_BUFFER, this._ubo);
            //ViewMatrix
            gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.getViewMatrix().m);
            //ProjectionMatrx
            gl.bufferSubData(gl.UNIFORM_BUFFER, 4 * 16, this.getProjectionMatrx().m);
            //eyePostion
            const posF32 = this._posF32arr;
            const pos = this.entity.transform.position;
            posF32[0] = pos.x;
            posF32[1] = pos.y;
            posF32[2] = pos.z;
            gl.bufferSubData(gl.UNIFORM_BUFFER, 4 * 32, posF32);
            //bind end
            gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        }

        /** 刷新 相机 asp */
        public reflashASP() {
            //canvas 尺寸
            let w = App.canvasElement.width;
            let h = App.canvasElement.height;
            //viewPort Rect设置
            let vp = this.viewPortRect;
            w *= vp.w;
            h *= vp.h;
            this._asp = w / h;
            if (isNaN(this._asp)) this._asp = 1;
        }

        /** 获取view 矩阵 */
        public getViewMatrix() {
            if (this.entity) {
                this.entity.transform.getMatrix()
                    .invertToRef(this._viewMatrix);
            }
            return this._viewMatrix;
        }

        /** 获取投影矩阵 */
        public getProjectionMatrx() {
            switch (this.projection) {
                case ProjectionType.PERSPECTIVE: this.calcPerspectiveProjMat(); break;
                case ProjectionType.ORTHOGRAPHIC: this.calcOrthographicProjMat(); break;
                default:
            }

            return this._projectionMatrix;
        }

        /**
        * 计算 相机视锥 顶点数据
        * @param near 锥体近平面
        * @param far 锥体远平面
        * @param outFrustum 视锥顶点数据列表(8个顶点位置坐标)
        */
        public calcFrustumPoints(near: number, far: number, outFrustum: Vector3[]) {
            if (!this.entity || !outFrustum) return;
            if (outFrustum.length < 8) {
                console.warn(` outFrustum array length less than 8.`);
                return;
            }
            //
            const fovIsH = this.fovAxis == FOVAxis.HORIZONTAL;
            const fov = this.fov;
            const asp = this.asp;
            // const near = cam.near;
            // const far = cam.far;
            const toWorldMat = this.entity.transform.getMatrix();
            // const toWorldMat = light.entity.transform.getMatrix();
            //通过 fov + near 求 rect 宽度
            let rhFov = fov * 0.5;
            let nHelfW = 0, nHelfH = 0, fHelfW = 0, fHelfH = 0;
            const tanVal = Math.tan(rhFov);
            if (fovIsH) {
                //near 平面
                nHelfW = tanVal * near;
                nHelfH = nHelfW / asp;
                //远 平面
                fHelfW = tanVal * far;
                fHelfH = fHelfW / asp;
            } else {
                nHelfH = tanVal * near;
                nHelfW = nHelfH * asp;
                //
                fHelfH = tanVal * far;
                fHelfW = fHelfH * asp;
            }

            //
            outFrustum[0].set(-nHelfW, nHelfH, near);       //nearLT
            outFrustum[1].set(nHelfW, nHelfH, near);        //nearRT
            outFrustum[2].set(nHelfW, -nHelfH, near);       //nearRD
            outFrustum[3].set(-nHelfW, -nHelfH, near);      //nearLD
            outFrustum[4].set(-fHelfW, fHelfH, far);        //farLT
            outFrustum[5].set(fHelfW, fHelfH, far);         //farRT
            outFrustum[6].set(fHelfW, -fHelfH, far);        //farRD
            outFrustum[7].set(-fHelfW, -fHelfH, far);       //farLD

            outFrustum.forEach((v) => {
                Vector3.TransformCoordinatesToRef(v, toWorldMat, v);
            });
        }

        /** 计算透视投影 */
        private calcPerspectiveProjMat() {
            Matrix.PerspectiveFovLHToRef(this.fov, this._asp, this.near, this.far, this._projectionMatrix, this.fovAxis == FOVAxis.VERTICAL);
        }

        /** 计算正交投影 */
        private calcOrthographicProjMat() {
            const w = this.size * this._asp;
            const h = this.size;
            Matrix.OrthoLHToRef(w, h, this.near, this.far, this._projectionMatrix);
        }
    }
}